/* Certificate creation for TPM EK.
 */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/pem.h>
#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/x509v3.h>
#include <trousers/tss.h>
#include <trousers/trousers.h>
#ifndef OPENSSL_NO_ENGINE
#include <openssl/engine.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <time.h>
#endif

#define TPMPASSWD "xenroot"

int read_tpm_cert(char* certfile);
void log_msg(char* file,int line,char *msg, ...);


#ifndef XEN_PRIVATE_KEY
#define XEN_PRIVATE_KEY "/etc/ssh/ssh_host_rsa_key"
#endif

#ifndef TPMCERTDIR
#define TPMCERTDIR "/opt/tpm/"
#endif
#ifndef TPMCERTFILE
#define TPMCERTFILE "/opt/tpm/tpm.cert"
#endif

#ifndef XENCERTFILE
#define XENCERTFILE "/opt/tpm/xentpm.cert"
#endif

#define XEN_PUBKEY "/opt/tpm/xen.pub"

#define LOG_FILE  "/tmp/xen_tpm_agent.log"

/* Generate a pu key in PEM format
 *openssl rsa -in ssh_host_rsa_key -pubout > /etc/tpm/xen.pub
 */

#define BSIZE	128
#define CKERR	if (result != TSS_SUCCESS) goto error

/* Definitions from section 7 of
 * TCG PC Client Specific Implementation Specification
 * For Conventional BIOS
 */
#define TCG_TAG_PCCLIENT_STORED_CERT		0x1001
#define TCG_TAG_PCCLIENT_FULL_CERT		0x1002
#define TCG_TAG_PCCLIENT_PART_SMALL_CERT	0x1003
#define TCG_FULL_CERT				0
#define TCG_PARTIAL_SMALL_CERT			1


#define TPMPASSWD "xenroot"

/*
 *Function tries to read a TPM
 *certificate from NV_RAM 
 * return 0 on success
 * 
 * */

FILE *log_filp;

int read_tpm_cert(char* certfile)
{
    TSS_HCONTEXT	hContext;
    TSS_HNVSTORE	hNV;
    FILE		    *f_out;
    UINT32	    	blobLen;
    UINT32		    nvIndex = TSS_NV_DEFINED|TPM_NV_INDEX_EKCert;
    UINT32		    offset;
    UINT32		    ekOffset;
    UINT32		    ekbufLen;
    BYTE		    *ekbuf;
    BYTE		    *blob;
    UINT32		    tag, certType;
    int		        result;

    result = Tspi_Context_Create(&hContext); CKERR;
    result = Tspi_Context_Connect(hContext, NULL); CKERR;
    result = Tspi_Context_CreateObject(hContext, TSS_OBJECT_TYPE_NV, 0, &hNV); CKERR;
    result = Tspi_SetAttribUint32(hNV, TSS_TSPATTRIB_NV_INDEX, 0, nvIndex); CKERR;

    /* Try reading certificate header from NV memory */
    blobLen = 5;
    result = Tspi_NV_ReadValue(hNV, 0, &blobLen, &blob);
    if (result != TSS_SUCCESS) {
        /* Try again with authorization */
        TSS_HPOLICY	hNVPolicy;
        result = Tspi_Context_CreateObject(hContext, TSS_OBJECT_TYPE_POLICY, TSS_POLICY_USAGE, &hNVPolicy); CKERR;
        result = Tspi_Policy_SetSecret(hNVPolicy,TSS_SECRET_MODE_PLAIN,
                (UINT32)strlen(TPMPASSWD),(BYTE*)TPMPASSWD);
        result = Tspi_Policy_AssignToObject(hNVPolicy, hNV); CKERR;
        blobLen = 5;
        result = Tspi_NV_ReadValue(hNV, 0, &blobLen, &blob);
    }
    if (result != TSS_SUCCESS) {
        // printf("Error %s\n",Trspi_Error_String(result));
        //log_msg(__FILE__,__LINE__,"Unable to read EK Certificate from TPM\n");
        goto error;
    }
    if (blobLen < 5)
        goto parseerr;
    tag = (blob[0]<<8) | blob[1];
    if (tag != TCG_TAG_PCCLIENT_STORED_CERT)
        goto parseerr;
    certType = blob[2];
    if (certType != TCG_FULL_CERT)
        goto parseerr;
    ekbufLen = (blob[3]<<8) | blob[4];
    /*	result = Tspi_Context_FreeMemory (hContext, blob); CKERR; */
    offset = 5;
    blobLen = 2;
    result = Tspi_NV_ReadValue(hNV, offset, &blobLen, &blob); CKERR;
    if (blobLen < 2)
        goto parseerr;
    tag = (blob[0]<<8) | blob[1];
    if (tag == TCG_TAG_PCCLIENT_FULL_CERT) {
        offset += 2;
        ekbufLen -= 2;
    } else if (blob[0] != 0x30)	/* Marker of cert structure */
        goto parseerr;
    /*	result = Tspi_Context_FreeMemory (hContext, blob); CKERR; */

    /* Read cert from chip in pieces - too large requests may fail */
    ekbuf = malloc(ekbufLen);
    ekOffset = 0;
    while (ekOffset < ekbufLen) {
        blobLen = ekbufLen-ekOffset;
        if (blobLen > BSIZE)
            blobLen = BSIZE;
        result = Tspi_NV_ReadValue(hNV, offset, &blobLen, &blob); CKERR;
        memcpy (ekbuf+ekOffset, blob, blobLen);
        /*		result = Tspi_Context_FreeMemory (hContext, blob); CKERR; */
        offset += blobLen;
        ekOffset += blobLen;
    }

    if ((f_out = fopen (certfile, "wb")) == NULL) {
        log_msg(__FILE__,__LINE__,"Unable to open '%s' for output\n", certfile);
        exit (1);
    }

    fwrite (ekbuf, 1, ekbufLen, f_out);
    fclose (f_out);
    return 0;

error:
    // log_msg(__FILE__,__LINE__,"Failure, error code: 0x%x\n", result);
    return 1;
parseerr:
    //log_msg(__FILE__,__LINE__,"Failure, unable to parse certificate store structure\n");
    return 2;
}


/* Read the TPM EK
 * TODO : generate one and insert if does not exist
 * 
 */

EVP_PKEY* get_tpm_ek()
{

    TSS_HCONTEXT	hContext;
    TSS_HTPM    	hTPM;
    TSS_RESULT	    result;
    TSS_HKEY	    hPubek;
    UINT32		    modulusLen;
    UINT32		    e_size;
    BYTE		    *modulus;
    BYTE            *e;
    EVP_PKEY        *pk;
    RSA			    *ekRsa;
    TSS_HPOLICY     ekPolicy;


    result = Tspi_Context_Create(&hContext);
    if (result != TSS_SUCCESS) {
        log_msg(__FILE__,__LINE__,"Error 0x%x on Tspi_Context_Create Unable to connect\n", result);
        exit(result);
    }
    result = Tspi_Context_Connect(hContext, NULL);
    if (result != TSS_SUCCESS) {
        log_msg(__FILE__,__LINE__,"Error 0x%x on Tspi_Context_Connect Unable to connectt\n", result);
        exit(result);
    }
    result = Tspi_Context_GetTpmObject (hContext, &hTPM);
    if (result != TSS_SUCCESS) {
        log_msg(__FILE__,__LINE__,"Error 0x%x on Tspi_Context_GetTpmObject\n", result);
        exit(result);
    }

    result = Tspi_TPM_GetPubEndorsementKey (hTPM, FALSE, NULL, &hPubek);

    if (result == TCPA_E_DISABLED_CMD) {

        result = Tspi_GetPolicyObject (hTPM, TSS_POLICY_USAGE, &ekPolicy);
        if (result != TSS_SUCCESS) {
            log_msg(__FILE__,__LINE__,"Error 0x%x on Tspi_Context_GetTpmObject\n", result);
            exit(result);
        }

        result = Tspi_Policy_SetSecret(ekPolicy, TSS_SECRET_MODE_PLAIN,
                (UINT32)strlen(TPMPASSWD),(BYTE*)TPMPASSWD);

        if (result != TSS_SUCCESS) {
            log_msg(__FILE__,__LINE__,"Error Setting TPM Password %s \n", Trspi_Error_String(result));
            exit(result);
        } 
        result = Tspi_TPM_GetPubEndorsementKey (hTPM, TRUE, NULL, &hPubek);
    }

    if (result != TSS_SUCCESS) {
            log_msg(__FILE__,__LINE__,"Error Reading TPM EK %s \n", Trspi_Error_String(result));
            log_msg(__FILE__,__LINE__,"Error Reading TPM EK, check the owner password after enabling the TPM \n");
	    return NULL;
	}

    result = Tspi_GetAttribData (hPubek, TSS_TSPATTRIB_RSAKEY_INFO,
            TSS_TSPATTRIB_KEYINFO_RSA_MODULUS, &modulusLen, &modulus);

    if (result != TSS_SUCCESS) {
        log_msg(__FILE__,__LINE__,"Error TPM EK RSA %s \n", Trspi_Error_String(result));
        return NULL;
    }

    if (modulusLen != 256) {
        Tspi_Context_FreeMemory (hContext, modulus);
        log_msg(__FILE__,__LINE__,"Error TPM EK RSA %s \n", Trspi_Error_String(result));
        return NULL;
    }

    result = Tspi_GetAttribData(hPubek, TSS_TSPATTRIB_RSAKEY_INFO,
            TSS_TSPATTRIB_KEYINFO_RSA_EXPONENT, &e_size, &e);

    if (result != TSS_SUCCESS) {
        log_msg(__FILE__,__LINE__,"Error 0x%x on Tspi_Context_GetAttr Exponent\n", result);
        Tspi_Context_FreeMemory (hContext, modulus);
        return NULL;
    }

    Tspi_Context_CloseObject (hContext, hPubek);
    ekRsa = RSA_new();
    ekRsa->n = BN_bin2bn (modulus,modulusLen, NULL);
    ekRsa->e = BN_new();
    ekRsa->e = BN_bin2bn(e, e_size, NULL);

    Tspi_Context_FreeMemory (hContext, modulus);
    Tspi_Context_FreeMemory (hContext, e);

    if ((pk=EVP_PKEY_new()) == NULL){
        RSA_free(ekRsa);
        log_msg(__FILE__,__LINE__,"%s","Error creating a EK EVP");
        return NULL;
    }
    if (!EVP_PKEY_assign_RSA(pk,ekRsa)){
        EVP_PKEY_free(pk);
        RSA_free(ekRsa);
        log_msg(__FILE__,__LINE__,"%s","Error inserting ek in EVP");
        return NULL;
    }

    return pk;
}


int main(int argc, char **argv)
{
    FILE * fp = NULL;
    int result;
    X509		*ekX509;
    EVP_PKEY	*ek_from_cert;
    EVP_PKEY    *ek_from_tpm;
    
    log_filp = stdout;//fopen(LOG_FILE,"a+");
    
    if (!log_filp) {
        exit(1);
    }

    /*read the cert */
    if (read_tpm_cert(TPMCERTFILE) == 0){
        log_msg(__FILE__,__LINE__,"%s"," EK Certificate Found in TPM\n");
    }  
    else {
        log_msg(__FILE__,__LINE__,"%s"," EK Certificate Found not found in TPM\n");
        exit(0);
    }
    /* cert not available in TPM
     * create the cert for EK from Xenserver private key
     * */
      
     
	if ((fp = fopen (TPMCERTFILE, "rb")) == NULL) {
		fprintf (stderr, "Unable to open file %s\n", TPMCERTFILE);
		exit (1);
	} 
   

    if (((ekX509 = d2i_X509_fp(fp,NULL)) == NULL)) {
        log_msg(__FILE__,__LINE__,"%s"," Unbale to read TPM Cert from file \n");
        exit(1);
    }
    
    fclose (fp);

    /**RSA Encryption	rsaEncryption	1.2.840.113549.1.1.1
     *RSAES Optimal Asymmetric Encryption Padding (OAEP)rsaes_oaep	1.2.840.113549.1.1.7
     * */

    X509_PUBKEY *pk = X509_get_X509_PUBKEY(ekX509);
	int algbufLen = i2d_X509_ALGOR(pk->algor, NULL);
	unsigned char *algbuf = malloc(algbufLen);
	unsigned char *algbufPtr = algbuf;
	i2d_X509_ALGOR(pk->algor, &algbufPtr);
	
    if (algbuf[12] == 7) {
             //fprintf(stdout,"%.*s\n", algbufLen, algbuf);
            algbuf[12] = 1;
	}
    algbufPtr = algbuf;
	d2i_X509_ALGOR(&pk->algor, (void *)&algbufPtr, algbufLen);
	free (algbuf);
	
    if ((ek_from_cert = X509_get_pubkey (ekX509)) == NULL) {
        log_msg(__FILE__,__LINE__,"%s"," Unbale to read TPM key fomr cert\n");
        exit(1);
    }


    ek_from_tpm = get_tpm_ek();
    
    if(!ek_from_tpm) {
        log_msg(__FILE__,__LINE__,"%s"," Unbale to read TPM key from  TPM \n");
        exit(1);
    }

    if(EVP_PKEY_cmp(ek_from_cert, ek_from_tpm) !=1 ) {
        log_msg(__FILE__,__LINE__,"%s"," TPM keys are different \n");
    }

    log_msg(__FILE__,__LINE__,"%s"," Keys are same\n");
    return(0);
}



void log_msg(char * file, int line, char *msg, ...)
{

		va_list argp;
        time_t t;  
        char buf[strlen(ctime(&t))+ 1];  
        time(&t);  
        snprintf(buf,strlen(ctime(&t)),"%s ", ctime(&t));  
        fprintf(log_filp, "%s ,%s, line %d: ",buf,file,line);
		va_start(argp, msg);
		vfprintf(log_filp, msg, argp);
		va_end(argp);
		fprintf(log_filp, "\n");
}



